import mp3 from 'assets/fish/bubbles.mp3'

class Seabed {
  constructor(canvas) {
    this.canvas = typeof canvas == 'string' ? document.querySelector(canvas) : canvas;
    this.width = this.canvas.width = this.canvas.offsetWidth;
    this.height = this.canvas.height = this.canvas.offsetHeight;
    this.ctx = this.canvas.getContext("2d");
    this.fishList = [];
    this.fgBubbleGroupList = Array(2).fill().map(() => new BubbleGroup(this.ctx));
    this.bgBubbleGroupList = Array(2).fill().map(() => new BubbleGroup(this.ctx));
    this.rafId = null;
    this.init();
  }

  resize() {
    this.width = this.canvas.width = this.canvas.offsetWidth;
    this.height = this.canvas.height = this.canvas.offsetHeight;
    this.fishList.forEach(fish => fish.resize());
    this.fgBubbleGroupList.forEach(bubbleGroup => bubbleGroup.resize());
    this.bgBubbleGroupList.forEach(bubbleGroup => bubbleGroup.resize());
  }

  loadImages(onprogress, onloaded) {
    var progress = 0; // 图片加载进度值
    var loadedSize = 0; // 已加载的图片大小
    var callback = size => { // 每次加载完成的回调
      loadedSize += size;
      progress = Math.round(loadedSize / totalSize * 100);
      onprogress(progress);
      progress == 100 && onloaded();
    };
    var bgSize = this.BG.W * this.BG.H;
    var totalSize = this.FISHES.reduce((pre, cur) => { // 待加载图片的总大小
      var size = cur.W * cur.H;
      cur.img = new Image();
      cur.img.src = cur.src;
      cur.img.onload = () => callback(size);
      return pre + size;
    }, bgSize);
    this.BG.img = new Image();
    this.BG.img.src = this.BG.src;
    this.BG.img.onload = () => callback(bgSize);
  }

  drawProgress(progress) { // canvas绘制下载进度
    this.ctx.clearRect(0, 0, this.width, this.height); // 清空canvas
    this.ctx.font = "80px helvetica"; // 设定canvas字体
    this.ctx.fillStyle = "#aaa"; // 设定canvas填充颜色
    var txt = progress + "%";
    var tw = this.ctx.measureText(txt).width;
    this.ctx.fillText(txt, (this.width - tw) / 2, (this.height + 80) / 2); // 填充文本
    this.ctx.strokeText(txt, (this.width - tw) / 2, (this.height + 80) / 2); // 描边文本
  }

  setBG() {
    this.ctx.clearRect(0, 0, this.width, this.height); // 清空canvas
    this.canvas.style.background = `url(${this.BG.src}) left center/auto 100%`;
  }

  play() {
    if (this.rafId) return;
    this.playing = true;
    let loop = () => {
      // 清空canvas
      this.ctx.clearRect(0, 0, this.width, this.height);

      // 绘制前景泡泡
      this.fgBubbleGroupList.forEach(bubbleGroup => {
        bubbleGroup.draw()
        bubbleGroup.draw()
        bubbleGroup.draw()
      })

      // 绘制所有的Fish对象
      this.fishList.forEach(fish => fish.draw())

      // 绘制背景泡泡
      this.bgBubbleGroupList.forEach(bubbleGroup => bubbleGroup.draw())

      // 循环绘制
      this.rafId = requestAnimationFrame(loop);
    }
    loop();
  }

  pause() {
    if (this.rafId) {
      cancelAnimationFrame(this.rafId);
      this.rafId = null;
    }
  }

  registerEvents() {
    this.canvas.onmousedown = e => { // 鼠标按下事件
      if (innerWidth >= 1160) {
        if (e.button == 0) { // 如果是左键
          this.addFish(1, e.offsetX, e.offsetY);
        } else if (e.button == 2) { // 如果是右键，fishList减少一个右键碰到的Fish对象
          this.removeFish(e.offsetX, e.offsetY);
        }
      } else {
        if (this.removeFish(e.offsetX, e.offsetY)) return;
        this.addFish(1, e.offsetX, e.offsetY);
      }
    }
    window.addEventListener('resize', () => this.resize());
  }

  getRandomFishInfo() { // 按照给定的概率随机获取一种鱼的信息
    var r = Math.floor(Math.random() * 100);
    return this.FISHES[
      r < 32 ? 0 :
        r < 45 ? 1 :
          r < 61 ? 2 :
            r < 75 ? 3 :
              r < 80 ? 4 :
                r < 88 ? 5 :
                  r < 93 ? 6 :
                    r < 96 ? 7 :
                      r < 98 ? 8 : 9
    ];
  }

  addFish(n = 1, cx, cy) {
    let newFishes = Array(n).fill().map(() => {
      let info = this.getRandomFishInfo();
      let x = cx ? cx - info.w / 2 : cx;
      let y = cy ? cy - info.h / 2 : cy;
      return new Fish(this.ctx, x, y, info);
    });
    this.fishList = this.fishList.concat(newFishes);
  }

  removeFish(cx, cy) {
    let idx = this.fishList.findIndex(fish => {
      let w = fish.info.w, h = fish.info.h;
      return cx > fish.x && cx < fish.x + w && cy > fish.y && cy < fish.y + h;
    });
    if (idx == -1) return false;
    this.fishList.splice(idx, 1);
    return true;
  }

  init() { // 初始化方法
    this.loadImages(progress => {
      this.drawProgress(progress);
    }, () => {
      this.addFish(10);
      this.setBG();
      this.registerEvents();
      this.play();
    });
  }

  BG = { src: require('assets/fish/sea.jpg'), W: 1060, H: 200 };//海洋背景图

  FISHES = [//保存各种鱼的图片资源
    { src: require('assets/fish/fish0.png'), W: 55, H: 296, w: 55, h: 37, f: 0, t: 3 },
    { src: require('assets/fish/fish1.png'), W: 78, H: 512, w: 78, h: 64, f: 0, t: 3 },
    { src: require('assets/fish/fish2.png'), W: 72, H: 448, w: 72, h: 56, f: 0, t: 3 },
    { src: require('assets/fish/fish3.png'), W: 77, H: 472, w: 77, h: 59, f: 0, t: 3 },
    { src: require('assets/fish/fish4.png'), W: 107, H: 976, w: 107, h: 122, f: 0, t: 3 },
    { src: require('assets/fish/fish5.png'), W: 105, H: 948, w: 105, h: 79, f: 0, t: 7 },
    { src: require('assets/fish/fish6.png'), W: 92, H: 1510, w: 92, h: 151, f: 0, t: 5 },
    { src: require('assets/fish/fish7.png'), W: 174, H: 1512, w: 174, h: 126, f: 0, t: 7 },
    { src: require('assets/fish/fish8.png'), W: 166, H: 2196, w: 166, h: 183, f: 0, t: 7 },
    { src: require('assets/fish/fish9.png'), W: 178, H: 1870, w: 178, h: 187, f: 0, t: 5 }
  ];
}

class Fish {
  constructor(ctx, x, y, info) {
    this.ctx = ctx;
    this.info = info; // 鱼的元数据
    this.resize(); // 计算画布尺寸和坐标的取值范围
    this.x = x || this.minX + Math.round(Math.random() * (this.maxX - this.minX)); // 鱼的左上顶点的x坐标
    this.y = y || this.minY + Math.round(Math.random() * (this.maxY - this.minY)); // 鱼的左上顶点的y坐标
    this.frame = this.info.f; // 绘制鱼用到的的当前帧，浮点型
    this.speed = 0.3 * Math.random(); // 鱼移动的速度和动画帧的切换速度，每个AnimationFrame时间游动的像素和动画切换的帧数
    this.maxSpeed = 0.3; // 鱼的最大速度
    this.minSpeed = 0; // 鱼的最小速度
    this.two_PI = 2 * Math.PI
    this.angle = this.two_PI * Math.random(); // 鱼游动的方向

    this.actionList = [
      () => ({
        name: '原地踏步',
        f: 0, // 当前动作时间点（单位：animationFrame）
        t: 30 + Math.floor(100 * Math.random()), // 动作结束时间点（单位：animationFrame）
        delta: { // delta存储鱼的速度和方向的变化量
          speed: -0.1 * Math.random(), // 过渡到该动作的加速度
          angle: 0, // 过渡到该动作的方向变化
        },
      }),
      () => ({
        name: '原地转圈',
        f: 0, // 当前动作时间点（单位：animationFrame）
        t: 30 + Math.floor(100 * Math.random()), // 动作结束时间点（单位：animationFrame）
        delta: { // delta存储鱼的速度和方向的变化量
          speed: -0.1 * Math.random(), // 过渡到该动作的加速度
          angle: 0.02 * Math.PI * Math.random() - 0.01 * Math.PI, // 过渡到该动作的方向变化
        },
      }),
      () => ({
        name: '闲逛1',
        f: 0, // 当前动作时间点（单位：animationFrame）
        t: 30 + Math.floor(100 * Math.random()), // 动作结束时间点（单位：animationFrame）
        delta: {
          speed: -0.05 + 0.1 * Math.random(),
          angle: 0.02 * Math.PI * Math.random() - 0.01 * Math.PI,
        },
      }),
      () => ({
        name: '闲逛2',
        f: 0, // 当前动作时间点（单位：animationFrame）
        t: 30 + Math.floor(100 * Math.random()), // 动作结束时间点（单位：animationFrame）
        delta: {
          speed: -0.1 + 0.2 * Math.random(),
          angle: 0.02 * Math.PI * Math.random() - 0.01 * Math.PI,
        },
      }),
    ];
    this.changeAction();
  }

  // 切换到下一个动作
  changeAction() {
    this.action = this.actionList[Math.floor(Math.random() * this.actionList.length)]();
  }

  // 执行一个动作
  doAction() {
    if (this.action.f++ == this.action.t) { // 表明动作结束，切换到下一个动作
      this.changeAction();
    }
    this.computeNextFrame(this.action.delta);
  }

  // 计算下一帧
  computeNextFrame(delta) { // delta存储鱼的速度和方向的变化量
    delta = Object.assign({
      speed: -0.05 + 0.1 * Math.random(),
      angle: 0.02 * Math.PI * Math.random() - 0.01 * Math.PI,
    }, delta);

    let nextSpeed = this.speed + delta.speed; // 计算下个速度
    this.speed =
      nextSpeed > this.maxSpeed ?
        this.maxSpeed :
        nextSpeed < this.minSpeed ? this.minSpeed : nextSpeed;

    let nextAngle = this.angle + delta.angle; // 计算下一个角度
    this.angle =
      nextAngle > this.two_PI ?
        nextAngle - this.two_PI :
        nextAngle < 0 ? nextAngle + this.two_PI : nextAngle;

    let nextX = this.x + this.speed * 10 * Math.cos(this.angle);
    let nextY = this.y + this.speed * 10 * Math.sin(this.angle);
    if (nextX >= this.minX && nextX <= this.maxX && nextY >= this.minY && nextY <= this.maxY) { // 正常位置
      this.x = nextX;
      this.y = nextY;
    } else { // 异常位置
      if (
        (nextX > this.maxX && nextX < this.x) ||
        (nextY > this.maxY && nextY < this.y) ||
        (nextX < this.minX && nextX > this.x) ||
        (nextY < this.minY && nextY > this.y)
      ) { // 进入画布
        this.x = nextX;
        this.y = nextY;
      } else { // 撞墙
        this.speed = 0;
      }
    }

    this.frame += 0.1 + this.speed * 0.5;
    if (this.frame > this.info.t) this.frame = this.info.f;
  }

  // 计算画布尺寸和坐标的取值范围
  resize() {
    this.W = this.ctx.canvas.width;
    this.H = this.ctx.canvas.height;
    if (this.info.w > this.info.h) {
      this.minX = 0;
      this.maxX = this.W - this.info.w;
      this.minY = (this.info.w - this.info.h) / 2;
      this.maxY = this.H - this.minY - this.info.h;
    } else {
      this.minX = (this.info.h - this.info.w) / 2;
      this.maxX = this.W - this.minX - this.info.w;
      this.minY = 0;
      this.maxY = this.H - this.info.h;
    }
  }

  draw() {
    let cx = this.x + this.info.w / 2; // 鱼的中心点x坐标
    let cy = this.y + this.info.h / 2; // 鱼的中心点y坐标
    this.ctx.translate(cx, cy); // 将画布的旋转中心点移动到鱼的中心点，此时该点的坐标变为(0,0)
    this.ctx.rotate(this.angle); // 旋转画布，然后开始画鱼，画布永远绕着(0,0)旋转

    // 下面开始画鱼
    // void ctx.drawImage(image, dx, dy);
    // void ctx.drawImage(image, dx, dy, dWidth, dHeight);
    // void ctx.drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight);
    let image = this.info.img;
    let sx = 0;
    let sy = this.info.h * Math.round(this.frame);
    let sWidth = this.info.w;
    let sHeight = this.info.h;
    let dx = -this.info.w / 2;
    let dy = -this.info.h / 2;
    let dWidth = this.info.w;
    let dHeight = this.info.h;
    this.ctx.drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight);

    this.ctx.rotate(-this.angle); // 画完鱼后，将画布旋转回原位
    this.ctx.translate(-cx, -cy); // 将画布的旋转中心点移动回原位

    this.doAction();
  }
}

class Bubble {
  constructor(ctx, x, y) { // 定义一个气泡对象的构造方法
    this.ctx = ctx;
    this.x = x; // 气泡圆心x坐标
    this.y = y; // 气泡圆心y坐标
    this.r = 8 * Math.random(); // 给定一个随机值气泡半径
    this.resize();
  }

  draw() { // 根据当前属性绘制气泡
    this.ctx.fillStyle = "rgba(255,255,255,0.5)"; // 设置填充色为半透明白色
    this.ctx.beginPath(); // 开始绘制路径
    this.ctx.arc(this.x, this.y, this.r, 0, Math.PI * 2); // 圆的路径
    this.ctx.closePath(); // 结束绘制路径
    this.ctx.fill(); // 填充闭合路径

    this.x += -3 + 6 * Math.random() + this.offset; // 更新x坐标为原来左边或右边，增加一个-3~3的随机值
    this.y -= 4 * Math.random(); // 更新y坐标，减少0~4之间的一个随机值
  }

  resize() {
    this.W = this.ctx.canvas.width;
    this.H = this.ctx.canvas.height;
    this.offset = (this.x - this.W / 2) / this.W * 2; // 气泡偏移率，靠左则左偏移，靠右则右偏移
  }
}

class BubbleGroup {
  constructor(ctx) { // 定义一个气泡清单对象的构造方法，该清单保存和管理一系列气泡对象
    this.ctx = ctx;
    this.bubbles = []; // bubbles属性保存Bubble对象
    this.resize();
  }

  draw() { // 根据当前属性绘制bubbles数组里面的所有Bubble对象
    if (Math.random() < .3) { // 每次有30%的概率生成一个新的气泡
      this.bubbles.push(new Bubble(this.ctx, this.x, this.y)); // 生成的新气泡保存在bubbles属性里面
    }
    for (let i = this.bubbles.length - 1; i > -1; i--) { // 遍历bubbles属性里保存的所有Bubble对象
      let bubble = this.bubbles[i];
      let x = bubble.x, y = bubble.y, r = bubble.r;
      if (x < -r || x > this.W + r || y < -r) this.bubbles.splice(i, 1); // 如果气泡越界，则将其从数组剔除
      else this.bubbles[i].draw(); // 绘制当前bubble
    }
    if (++this.counter >= this.counterTimes) { // 如果持续时间到了
      this.resize();
    }
  }

  resize() {
    this.W = this.ctx.canvas.width;
    this.H = this.ctx.canvas.height;
    this.x = this.W * Math.random(); // 随机生成产生气泡位置的x坐标
    this.y = this.H * Math.random(); // 随机生成产生气泡位置的y坐标
    this.counter = 0; // 一直在某个固定位置产生气泡的计时器
    this.counterTimes = 100 + 200 * Math.random(); // 一直在某个固定位置产生气泡的预定持续时间
    this.bubbles.forEach(bubble => bubble.resize());
  }
}

$.fn.banner = function () {
  var $audio = $(`<audio src="${mp3}" id="bubbleSound" loop></audio>`);
  var audio = $audio[0];
  var $canvas = $(`<canvas style="display:block;width:100%;height:100%;" oncontextmenu="return false"></canvas>`);
  $canvas.on('click', () => audio.paused && audio.play());
  this.addClass('container').append($audio, $canvas);
  new Seabed($canvas[0]);
};